#include "logger.h"    // this class.

#include <fstream>     // std::ofstream
#include <Windows.h>   // GetSystemTime
#include <stdarg.h>    // va_list, va_end  
#include <direct.h>    // mkdir
#include <io.h>        // access  
#include <map>         // map 

namespace MCCommon
{

static const int kBufMaxSize = 256;

static const long kMaxFileSize = 1024*1024*1024; // 1M

static const char* kDefaultName = "log/log.txt";

std::string
FormatString
(
    const char* format,
    ...
)
{
    char buf[ kBufMaxSize ] = {0};

    va_list vaList;    
    va_start( vaList, format );    
    vsprintf_s( buf, kBufMaxSize, format, vaList );
    va_end( vaList );

    return std::string( buf );
}

Logger& Logger::Instance()
{    
    static Logger logger;
    return logger;
}

Logger::Logger()
:   fFileName( kDefaultName ),
    fTraceLevel( kTL_Info ),
    fCreateFileRule( kFCR_Date ),
    fMaxFileSize( kMaxFileSize )     
{    
}

Logger::Logger
(
    const std::string& name
)
:   fFileName( name ),
    fTraceLevel( kTL_Info ),
    fCreateFileRule( kFCR_Date ),
    fMaxFileSize( kMaxFileSize )     
{    
}

Logger::~Logger()
{
}

void 
Logger::SetLogName
( 
    const std::string& name 
)
{
    fFileName = name;

    CreateStreamFile( std::ios::in|std::ios::out|std::ios::app );
}

void 
Logger::SetTraceLevel
(
    TraceLevel traceLevel
)
{
    fTraceLevel = traceLevel;
}

void 
Logger::SetFileCreateRule
(
    FileCreateRule fileRule
)
{
    fCreateFileRule = fileRule;
}

void 
Logger::SetMaxFileSize
(
    long maxFileSize
)
{
    fMaxFileSize = maxFileSize;
}

void 
Logger::AddLog
(    
    const std::string& strlog,
    TraceLevel traceLevel/* = kTL_Info*/ 
)
{
    if ( traceLevel >= fTraceLevel )
    {
        // Create stream file if the stream is not opened.
        if ( !fStream.is_open() )
        {
            CreateStreamFile( std::ios::in|std::ios::out|std::ios::app );
        }

        // Get current time.
        SYSTEMTIME st;
        ::GetLocalTime( &st );

        // Apply log file rule.
        ApplyLogRule( st  );

        // Format time to string.
        std::string timeStr = FormatString( 
            "%04d-%02d-%02d %02d:%02d:%02d,%03d", 
            st.wYear, st.wMonth, st.wDay,
            st.wHour, st.wMinute, st.wSecond,
            st.wMilliseconds );
       
        // Write log to file.
        fStream << timeStr << " ["<<(int)traceLevel<<"] "
            << strlog.c_str() << std::endl;

        // Flush to stream.
        fStream.flush();
    }
}

void 
Logger::MakeDirectory
(
    const std::string& name
)
{
        std::string path;

        size_t pos = name.find_first_of( "/\\", 0 );
        while( pos != std::string::npos )
        {
            path = name.substr(0, pos );

            if ( access( path.c_str(), 0 ) != 0 )
            {
                mkdir( path.c_str() );
            }

            pos = name.find_first_of( "/\\", pos+1 );
        }
}

void Logger::CreateStreamFile
(
    std::ios::openmode openMode
)
{
    // Create the new file name.
    std::string fileName;
    if ( fCreateFileRule == kFCR_Date )
    {  
        //  Get current time.
        SYSTEMTIME st;
        ::GetLocalTime( &st ); 

        fStreamOpenedTime = st;       
           
        // Add date to file name.
        size_t pos = fFileName.find_last_of( ".");
        if ( pos != std::string::npos )
        {
            std::string name = fFileName.substr( 0, pos );
            std::string ext = fFileName.substr( pos );

            fileName = name + FormatString("%04d-%02d-%02d", 
                st.wYear, st.wMonth, st.wDay ) + ext;
        }
    }
    else 
    {
        fileName = fFileName;
    }

    // Create directory if not existed.
    MakeDirectory( fileName );  

    // Open file.
    if ( fStream.is_open() )
    {
        fStream.close();
        fStream.clear();
    }

    fStream.open( fileName.c_str(), openMode );    
}

void
Logger::ApplyLogRule
(
    const SYSTEMTIME& st 
)
{
    if ( fCreateFileRule == kFCR_Date )
    { //  create a new file if date.

        if ( st.wYear != fStreamOpenedTime.wYear ||
            st.wMonth != fStreamOpenedTime.wMonth ||
            st.wDay != fStreamOpenedTime.wDay )
        {
            CreateStreamFile( std::ios::in|std::ios::out|std::ios::app );
        }
    }
    else if ( fCreateFileRule == kFCR_Fixed )
    {
        fStream.seekg( 0 ,std::ios_base::end );
        long length = fStream.tellg();

        if ( length >= fMaxFileSize )
        {
            // Calculate the left length.
            long leftLength = fMaxFileSize/2;

            // Seek to begin of left file 
            long begPos = length - leftLength;            
            fStream.seekg( begPos, std::ios_base::beg );
            
            // Skip current line.
            char lineBuf[ kBufMaxSize ];
            fStream.getline( lineBuf, kBufMaxSize );

            // Read the left file to a buffer.
            bool good = fStream.good();
            char* pBuf = new char[ leftLength ]; 
            memset( pBuf, 0, leftLength );           
            fStream.read( pBuf, leftLength );
            long readCount = fStream.gcount();
            if (  fStream.fail() )
            {
                fStream.clear();                
            }
           
            // Erase all the original content.
            CreateStreamFile( std::ios::in|std::ios::out|std::ios::trunc );
            
            // Write the left buf to the new file.
            fStream.write( pBuf, readCount );          
            fStream.flush();
            
            delete[] pBuf;
        }
    }
}

}// end of namespace MCCommon.